package com.googlecode.kipler.satisfiability.completion.strategy;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.googlecode.kipler.container.dl.ABox;
import com.googlecode.kipler.container.dl.Individual;
import com.googlecode.kipler.container.dl.SHIQABox;
import com.googlecode.kipler.container.dl.World;
import com.googlecode.kipler.container.dl.WorldConstraintSystem;
import com.googlecode.kipler.container.dl.operational.FilterType;
import com.googlecode.kipler.container.dl.operational.RelatedIndividualWithTypeFilter;
import com.googlecode.kipler.container.dl.operational.RelatedIndividualWithTypeFilterFactory;
import com.googlecode.kipler.satisfiability.blocking.abox.ABoxBlockingStrategy;
import com.googlecode.kipler.satisfiability.blocking.abox.SubsetBlocking;
import com.googlecode.kipler.satisfiability.completion.rule.CompletionRule;
import com.googlecode.kipler.satisfiability.completion.rule.EmptyDomainRule;
import com.googlecode.kipler.satisfiability.completion.rule.individual.ABoxRule;
import com.googlecode.kipler.satisfiability.completion.rule.individual.AllValuesRule;
import com.googlecode.kipler.satisfiability.completion.rule.individual.AtLeastRule;
import com.googlecode.kipler.satisfiability.completion.rule.individual.AtMostRule;
import com.googlecode.kipler.satisfiability.completion.rule.individual.ChooseRule;
import com.googlecode.kipler.satisfiability.completion.rule.individual.IntersectionRule;
import com.googlecode.kipler.satisfiability.completion.rule.individual.LazyUnfoldingRule;
import com.googlecode.kipler.satisfiability.completion.rule.individual.NegatedConceptDefinitionRule;
import com.googlecode.kipler.satisfiability.completion.rule.individual.NegatedRoleInclusionRule;
import com.googlecode.kipler.satisfiability.completion.rule.individual.PositiveConceptDefinitionRule;
import com.googlecode.kipler.satisfiability.completion.rule.individual.SHIQABoxBothRootMergeStrategy;
import com.googlecode.kipler.satisfiability.completion.rule.individual.SHIQABoxNotBothRootMergeStrategy;
import com.googlecode.kipler.satisfiability.completion.rule.individual.SomeValuesRule;
import com.googlecode.kipler.satisfiability.completion.rule.individual.TransitivityRule;
import com.googlecode.kipler.satisfiability.completion.rule.individual.UnionRule;
import com.googlecode.kipler.satisfiability.reasoner.Reasoner;
import com.googlecode.kipler.satisfiability.reasoner.ReasonerException;

/**
 * 
 * @author İnanç Seylan
 * 
 * TODO: This class definitely needs some rethinking about rule application to
 * root nodes.
 */
public class SHQABoxDepthFirstCompletionStrategy implements CompletionStrategy {

	private List<ABoxRule> aboxRuleList = new ArrayList<ABoxRule>();

	private List<ABoxRule> rootNodeRuleList = new ArrayList<ABoxRule>();

	// TODO: create a separate category for shrinking root node rule

	private Reasoner reasoner;

	private World currentWorld;

	private WorldConstraintSystem currentWcs;

	private boolean cleanTree;

	public SHQABoxDepthFirstCompletionStrategy(Reasoner reasoner,
			boolean cleanTree) {
		setReasoner(reasoner);
		this.cleanTree = cleanTree;
		ABoxBlockingStrategy blocking = new SubsetBlocking();
		RelatedIndividualWithTypeFilter filterer = RelatedIndividualWithTypeFilterFactory
				.getFilter(FilterType.TYPED_NEIGHBOR);

		aboxRuleList.add(new LazyUnfoldingRule());
		aboxRuleList.add(new PositiveConceptDefinitionRule(blocking));
		aboxRuleList.add(new IntersectionRule(blocking));
		aboxRuleList.add(new AllValuesRule(blocking));
		aboxRuleList.add(new TransitivityRule(blocking));
		aboxRuleList.add(new UnionRule(getReasoner(), blocking));
		aboxRuleList.add(new ChooseRule(getReasoner(), blocking));
		aboxRuleList.add(new AtMostRule(getReasoner(), blocking,
				new SHIQABoxNotBothRootMergeStrategy()));
		aboxRuleList.add(new SomeValuesRule(blocking, filterer, true));
		aboxRuleList.add(new AtLeastRule(blocking, filterer, true));

		rootNodeRuleList.add(new LazyUnfoldingRule());
		rootNodeRuleList.add(new PositiveConceptDefinitionRule(blocking));
		rootNodeRuleList.add(new IntersectionRule(blocking));
		rootNodeRuleList.add(new AllValuesRule(blocking));
		rootNodeRuleList.add(new TransitivityRule(blocking));
		rootNodeRuleList.add(new UnionRule(getReasoner(), blocking));
		rootNodeRuleList.add(new ChooseRule(getReasoner(), blocking));
		rootNodeRuleList.add(new AtMostRule(getReasoner(), blocking,
				new SHIQABoxBothRootMergeStrategy()));
	}

	/**
	 * @return the reasoner
	 */
	public Reasoner getReasoner() {
		return reasoner;
	}

	/**
	 * @param reasoner
	 *            the reasoner to set
	 */
	public void setReasoner(Reasoner reasoner) {
		this.reasoner = reasoner;
	}

	public void apply(WorldConstraintSystem currentWcs)
			throws ReasonerException {
		currentWorld = currentWcs.worldIterator().next();
		this.currentWcs = currentWcs;
		applyNonGeneratingRulesToRootNodes();
		CompletionRule compRule = new NegatedRoleInclusionRule(currentWcs,
				currentWorld);
		compRule.apply();
		compRule = new NegatedConceptDefinitionRule(currentWcs, currentWorld);
		compRule.apply();
		compRule = new EmptyDomainRule(currentWcs, currentWorld);
		compRule.apply();
		for (Individual root : getRootNodes()) {
			applyRulesDepthFirst(root);
		}
	}

	private void applyNonGeneratingRulesToRootNodes() throws ReasonerException {
		for (Individual root : getRootNodes()) {
			expandLabelByRuleList(rootNodeRuleList, root);
		}
	}

	private void applyRulesDepthFirst(Individual indv) throws ReasonerException {
		expandLabelByRuleList(aboxRuleList, indv);
		ABox abox = currentWorld.getABox();
		Iterator<Individual> i = abox.successorIterator(indv);
		while (i.hasNext()) {
			Individual nextIndv = i.next();
			applyRulesDepthFirst(nextIndv);
		}
		if (cleanTree) {
			removeSubtree(indv, abox);
		}
	}

	private void removeSubtree(Individual indv, ABox abox) {
		Iterator<Individual> i = abox.successorIterator(indv);
		while (i.hasNext()) {
			Individual nextIndv = i.next();
			abox.remove(nextIndv.getName());
		}
	}

	private void expandLabelByRuleList(List<ABoxRule> ruleList, Individual indv)
			throws ReasonerException {
		boolean ruleApplied;
		do {
			ruleApplied = false;
			Iterator<ABoxRule> ruleIter = ruleList.iterator();
			while (ruleIter.hasNext() && !ruleApplied) {
				ABoxRule rule = ruleIter.next();
				rule.setWcs(currentWcs);
				rule.setWorld(currentWorld);
				rule.setIndividual(indv);
				rule.apply();
				ruleApplied = ruleApplied || rule.isApplied();
			}
		} while (ruleApplied);
	}

	// TODO: make this faster by storing the root nodes in a list in ABox
	private List<Individual> getRootNodes() {
		List<Individual> result = new ArrayList<Individual>();
		SHIQABox aBox = (SHIQABox) currentWorld.getABox();
		for (Iterator<Individual> i = aBox.individualIterator(); i.hasNext();) {
			Individual indv = i.next();
			if (aBox.isRoot(indv)) {
				result.add(indv);
			}
		}
		return result;
	}

}
